#!/usr/bin/env python
# coding: utf-8

# # 模型训练
# 
# 通过上面章节的学习，我们已经学会如何创建模型和构建数据集，现在开始学习如何设置超参和优化模型参数。
# 
# ## 超参（Hyper-parametric）
# 
# 超参是可以调整的参数，可以控制模型训练优化的过程，不同的超参数值可能会影响模型训练和收敛速度。目前深度学习模型多采用批量随机梯度下降算法进行优化，随机梯度下降算法的原理如下：
# 
# $$w_{t+1}=w_{t}-\eta \frac{1}{n} \sum_{x \in \mathcal{B}} \nabla l\left(x, w_{t}\right)$$
# 
# 式中，$n$是批量大小(batch size)，$η$是学习率(learning rate)；另外，$w_{t}$为训练轮次t中权重参数，$\nabla l$为损失函数的导数。可知道除了梯度本身，这两个因子直接决定了模型的权重更新，从优化本身来看它们是影响模型性能收敛最重要的参数。一般会定义以下超参用于训练：
# 
# 训练轮次（epoch）：训练时遍历数据集的次数。
# 
# 批次大小（batch size）：数据集进行分批读取训练，设定每个批次数据的大小。batch size过小，花费时间多，同时梯度震荡严重，不利于收敛；batch size过大，不同batch的梯度方向没有任何变化，容易陷入局部极小值，因此需要选择合适的batch size，可以有效提高模型精度、全局收敛。
# 
# 学习率（learning rate）：如果学习率偏小，会导致收敛的速度变慢，如果学习率偏大则可能会导致训练不收敛等不可预测的结果。梯度下降法是一个广泛被用来最小化模型误差的参数优化算法。梯度下降法通过多次迭代，并在每一步中最小化损失函数来估计模型的参数。学习率就是在迭代过程中，会控制模型的学习进度。
# 
# ![learning-rate](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r1.7/tutorials/source_zh_cn/beginner/images/learning_rate.png)

# In[1]:


epochs = 10
batch_size = 32
momentum = 0.9
learning_rate = 1e-2


# ## 损失函数
# 
# **损失函数**用来评价模型的**预测值**和**目标值**之间的误差，在这里，使用绝对误差损失函数`L1Loss`：
# 
# $$\text { L1 Loss Function }=\sum_{i=1}^{n}\left|y_{true}-y_{predicted}\right|$$
# 
# `luojianet.nn.loss`也提供了许多其他常用的损失函数，如`SoftmaxCrossEntropyWithLogits`、`MSELoss`、`SmoothL1Loss`等。
# 
# 我们给定预测值和目标值，通过损失函数计算预测值和目标值之间的误差（损失值），使用方法如下所示：

# In[2]:


import numpy as np
import luojianet.nn as nn
from luojianet import Tensor

loss = nn.L1Loss()
output_data = Tensor(np.array([[1, 2, 3], [2, 3, 4]]).astype(np.float32))
target_data = Tensor(np.array([[0, 2, 5], [3, 1, 1]]).astype(np.float32))

print(loss(output_data, target_data))


# ## 优化器函数
# 
# 优化器函数用于计算和更新梯度，模型优化算法的选择直接关系到最终模型的性能。有时候最终模型效果不好，未必是特征或者模型设计的问题，很有可能是优化算法的问题。
# 
# luojianet所有优化逻辑都封装在`Optimizer`对象中，在这里，我们使用`Momentum`优化器。`luojianet.nn`也提供了许多其他常用的优化器函数，如`Adam`、`SGD`、`RMSProp`等。
# 
# 我们需要构建一个`Optimizer`对象，这个对象能够基于计算得到的梯度对参数进行更新。为了构建一个`Optimizer`，需要给它一个包含可优化的参数，如网络中所有可以训练的`parameter`，即设置优化器的入参为`net.trainable_params()`。
# 
# 然后，设置`Optimizer`的参数选项，比如学习率、权重衰减等。代码样例如下：

# In[3]:


from luojianet import nn
from src.lenet import lenet

net = lenet()
optim = nn.Momentum(net.trainable_params(), learning_rate, momentum)


# ## 模型训练
# 
# 模型训练一般分为四个步骤：
# 
# 1. 构建数据集。
# 2. 定义神经网络。
# 3. 定义超参、损失函数及优化器。
# 4. 输入训练轮次和数据集进行训练。
# 
# 模型训练示例代码如下：

# In[4]:


import luojianet.nn as nn
from luojianet.train import Model

from luojianet.dataset import MnistDataset
from src.lenet import lenet
from luojianet.train.callback import LossMonitor
from luojianet.dataset.vision.c_transforms import RandomHorizontalFlip, HWC2CHW
from luojianet.dataset.transforms.c_transforms import TypeCast
from luojianet.common import dtype as mstype
# 1. 构建数据集
dataset=MnistDataset(dataset_dir='./MNIST_data',usage='train')
# 数据增强
trans = [
    RandomHorizontalFlip(prob=0.5),
    HWC2CHW(),
    TypeCast(mstype.float32)
]

convert = [
    TypeCast(mstype.int32)
]

dataset = dataset.map(operations=trans, input_columns=['image'])
dataset = dataset.map(operations=convert, input_columns=['label']).batch(batch_size, True)
'''
for i in range(0,4):
    data = next(dataset.create_dict_iterator())
'''
# 2. 定义神经网络
network = lenet()
# 3.1 定义损失函数
net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')
# 3.2 定义优化器函数
net_opt = nn.Momentum(network.trainable_params(), learning_rate=learning_rate, momentum=momentum)
# 3.3 初始化模型参数
model = Model(network, loss_fn=net_loss, optimizer=net_opt, metrics={'acc'})

# 4. 对神经网络执行训练
model.train(epochs, dataset, callbacks=LossMonitor(1875))


# 训练过程中会打印loss值，loss值会波动，但总体来说loss值会逐步减小，精度逐步提高。每个人运行的loss值有一定随机性，不一定完全相同。
